#ifndef _SIMPLE_LOGGER_H
#define _SIMPLE_LOGGER_H
#include <string>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <pthread.h>
#include <list>
#include <thread>
#include <fstream>
#include <sstream>
#include <vector>
#include <functional>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <pthread.h>
#include <sys/time.h>
#include <string.h>
#include <condition_variable>
#include <cstddef>

namespace simplelogger
{

    class LoggerAppendDataInfo
    {
    public:
        std::string fileName;
        int line = 0;
        std::string funcName;
    };

    class ILoggerAppendData
    {
    public:
        virtual std::string toString(const LoggerAppendDataInfo &data) = 0;
        virtual void onUnload() {}
    };
    enum LogLevelType
    {
        LOGGER_DEFAULT = -1,   /**< Default log level */
        LOGGER_OFF = 0x00,     /**< Log level off */
        LOGGER_FATAL = 0x01,   /**< fatal system error */
        LOGGER_ERROR = 0x02,   /**< error with impact to correct functionality */
        LOGGER_WARN = 0x03,    /**< warning, correct behaviour could not be ensured */
        LOGGER_INFO = 0x04,    /**< informational */
        LOGGER_DEBUG = 0x05,   /**< debug  */
        LOGGER_VERBOSE = 0x06, /**< highest grade of information */
        LOGGER_MAX             /**< maximum value, used for range check */

    };

    class ILoggerSink
    {
    public:
        virtual ~ILoggerSink() = default;
        virtual void printLine(int level, const char *) = 0;
        virtual void onUnload() {}
    };
    class ILoggerPrint
    {
    public:
        virtual void logger(LoggerAppendDataInfo *data, LogLevelType nLogLevel, const char *pFormat, ...) = 0;
    };
    class LogStream final
    {
    public:
        LogStream() = delete;

        /**
         * @brief Creates the message stream object.
         *
         * Initiates it with the given log level directly on the back-end.
         *
         * @param[in] logLevel  The severity level of this message
         * @param[in] logger    The associated logger context
         */
        LogStream(LogLevelType logLevel, const ILoggerPrint *logger) noexcept
        {
            this->logLevel = logLevel;
            this->logger = (ILoggerPrint *)logger;
        }

        LogStream(const LogStream &a)
        {
            this->logLevel = a.logLevel;
            this->logger = a.logger;
        }
        LogStream &operator=(const LogStream &) = delete;

        LogStream &operator=(LogStream &&) = delete;
        /**
         * @brief Flushes the log message before destroying the stream object.
         *
         * Unless it was not explicitly flushed before, by calling @c Flush().
         */
        ~LogStream()
        {
            Flush();
        }

        // [SWS_LOG_00039]{DRAFT} Definition of API function ara::log::Log Stream::Flush
        // Sends out the current log buffer and initiates a new message stream. .
        void Flush() noexcept
        {
            if (!this->stream.str().empty())
            {
                std::stringstream oss;
                oss << this->buildExtraInfo();
                oss << stream.str();

                LoggerAppendDataInfo __data__;

                if (logger)
                    logger->logger(&__data__, this->logLevel, "%s", oss.str().c_str());
            }
            this->stream.str().clear();
        }

        /// @traceid{SWS_LOG_00040}
        LogStream &operator<<(bool value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00041}
        LogStream &operator<<(std::uint8_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00042}
        LogStream &operator<<(std::uint16_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00043}
        LogStream &operator<<(std::uint32_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00044}
        LogStream &operator<<(std::uint64_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00045}
        LogStream &operator<<(std::int8_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00046}
        LogStream &operator<<(std::int16_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00047}
        LogStream &operator<<(std::int32_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00048}
        LogStream &operator<<(std::int64_t value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00049}
        LogStream &operator<<(float value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00050}
        LogStream &operator<<(double value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @traceid{SWS_LOG_00051}
        LogStream &operator<<(const char *const value) noexcept
        {
            stream << value;
            return *this;
        }

        /// @}

        /// @traceid{SWS_LOG_00129}
        LogStream &WithLocation(const std::string &file, int line) noexcept
        {
            this->file = file.data();
            for (int i = file.length(); i >= 0; i--)
            {
                if (file[i] == '/' || file[i] == '\\')
                {
                    this->file = std::string(&file[i + 1]);
                    break;
                }
            }

            this->line = line;
            return *this;
        }

        // [SWS_LOG_00132]{DRAFT} Definition of API function ara::log::LogStream::With Tag
        LogStream &WithTag(const std::string &tag) noexcept
        {
            this->tags.push_back(tag.data());
            return *this;
        }

        template <typename T>
        LogStream &operator<<(const T &arg) noexcept
        {
            stream << arg;
            return *this;
        }

    private:
        std::ostringstream stream;
        LogLevelType logLevel;
        std::vector<std::string> tags;
        std::string file;
        int line = 0;
        std::string buildExtraInfo()
        {
            std::stringstream ss;
            if (!file.empty())
                ss << file << ":" << line << " ";
            if (tags.size())
            {
                ss << "[";
                for (auto i = tags.begin(); i != tags.end(); i++)
                {
                    if (i + 1 != tags.end())
                        ss << *i << " ";
                    else
                        ss << *i;
                }
                ss << "] ";
            }

            return ss.str();
        }
        ILoggerPrint *logger = nullptr;
    };

    LogStream &operator<<(LogStream &out, const void *value) noexcept;

    class ILogger : public ILoggerPrint
    {
    public:
        virtual void logger(LoggerAppendDataInfo *data, LogLevelType nLogLevel, const char *pFormat, ...)
        {
            unsigned int ret = 0;
            char buf[512] = {0};
            // pthread_t tid = pthread_self();

            va_list arglist;
            va_start(arglist, pFormat);
            // Format the log as a string
            ret = vsnprintf(buf, sizeof(buf) / sizeof(buf[0]), pFormat, arglist);
            va_end(arglist);

            log(data, nLogLevel, buf);
            (void)ret;

            return;
        }
        virtual void log(LoggerAppendDataInfo *data, LogLevelType nLogLevel, const std::string &content) = 0;
        virtual void onUnload() {}
        LogStream WithLevel(LogLevelType logLevel) const noexcept
        {
            return LogStream(logLevel, this);
        }
    };

#define LOG(LOGLEVEL, FORMAT, ARGS...)                         \
    do                                                         \
    {                                                          \
        simplelogger::LoggerAppendDataInfo __data__;           \
        __data__.fileName = __FILE__;                          \
        __data__.funcName = __FUNCTION__;                      \
        __data__.line = __LINE__;                              \
        auto bean = simplelogger::Logger::Get();               \
        if (bean)                                              \
            bean->logger(&__data__, LOGLEVEL, FORMAT, ##ARGS); \
    } while (0);

#define LOGGER_WARN(FORMAT, ARGS...) LOG(simplelogger::LOGGER_WARN, FORMAT, ##ARGS)
#define LOGGER_FATAL(FORMAT, ARGS...) LOG(simplelogger::LOGGER_FATAL, FORMAT, ##ARGS)
#define LOGGER_INFO(FORMAT, ARGS...) LOG(simplelogger::LOGGER_INFO, FORMAT, ##ARGS)
#define LOGGER_DEBUG(FORMAT, ARGS...) LOG(simplelogger::LOGGER_DEBUG, FORMAT, ##ARGS)

#define LogD(FORMAT, ARGS...) LOG(simplelogger::LOGGER_DEBUG, FORMAT, ##ARGS)
#define LogI(FORMAT, ARGS...) LOG(simplelogger::LOGGER_INFO, FORMAT, ##ARGS)
#define LogF(FORMAT, ARGS...) LOG(simplelogger::LOGGER_FATAL, FORMAT, ##ARGS)
#define LogW(FORMAT, ARGS...) LOG(simplelogger::LOGGER_WARN, FORMAT, ##ARGS)
#define LogA(FORMAT, ARGS...) LOG(simplelogger::LOGGER_FATAL, FORMAT, ##ARGS)
#define LogE(FORMAT, ARGS...) LOG(simplelogger::LOGGER_ERROR, FORMAT, ##ARGS)

#define LOG_STREAM(LVL) simplelogger::Logger::Get()->WithLevel(LVL)

#define LOG_INFO_STREAM simplelogger::Logger::Get()->WithLevel(simplelogger::LOGGER_INFO).WithLocation(__FILE__, __LINE__).WithTag(__func__)
#define LOG_FATAL_STREAM simplelogger::Logger::Get()->WithLevel(simplelogger::LOGGER_FATAL).WithLocation(__FILE__, __LINE__).WithTag(__func__)

    class LoggerAppendData : public ILoggerAppendData
    {
    public:
        std::string toString(const LoggerAppendDataInfo &data)
        {
            char buf[256] = {'\0'};
            pthread_t tid = pthread_self();
#define get_filename(x) (strrchr(x, '\\') ? strrchr(x, '\\') + 1 : x)
#define get_filename1(x) strrchr(x, '/') ? strrchr(x, '/') + 1 : x
            if (data.line != 0)
                sprintf(buf, "Thr[%lu] %s:%d ::%s \t", (unsigned long)tid, get_filename1(get_filename(data.fileName.c_str())), data.line, data.funcName.c_str());
            else
                sprintf(buf, "Thr[%lu] ", (unsigned long)tid);
#undef get_filename
            return buf;
        }
        static std::string getTimestamp()
        {
            time_t tt = time(NULL);
            struct tm *stm = localtime(&tt);

            struct timeval tv;
            gettimeofday(&tv, NULL);
            int ms = tv.tv_usec / 1000;

            char timestamp[24] = {0};
            sprintf(timestamp, "%02d-%02d %02d:%02d:%02d.%03d", 1 + stm->tm_mon, stm->tm_mday, stm->tm_hour,
                    stm->tm_min, stm->tm_sec, ms);

            return timestamp;
        }
    };

    class LoggerConsoleSink : public ILoggerSink
    {
    public:
        void printLine(int level, const char *buf) override
        {
            if (disableTag)
                return;
            auto fmt = "%s\tunknow: %s\n";
            switch (level)
            {
            case LOGGER_FATAL:
                fmt = "%s\t[fatal] %s\n";
                break;
            case LOGGER_ERROR:
                fmt = "%s\t[error] %s\n";
                break;
            case LOGGER_WARN:
                fmt = "%s\t[warn ] %s\n";
                break;
            case LOGGER_INFO:
                fmt = "%s\t[info ] %s\n";
                break;
            case LOGGER_DEBUG:
                fmt = "%s\t[debug] %s\n";
                break;
            case LOGGER_VERBOSE:
                fmt = "%s\t[verbo] %s\n";
                break;
            default:
                break;
            }

            std::string timestamp = LoggerAppendData::getTimestamp();

            printf(fmt, timestamp.c_str(), buf);
            return;
        }
        void disable()
        {
            disableTag = true;
        }
        bool disableTag = false;
    };
#if defined(__QNX__)
#include <sys/slog2.h>
#define LOG_BUFFER_SET_NAME "my_app_log"
#define LOG_BUFFER_NAME "my_log_buffer"
    class SLoggerSink : public ILoggerSink
    {
    public:
        SLoggerSink()
        {
            slog2_buffer_set_config_t config = {
                .num_buffers = 1,
                .buffer_set_name = LOG_BUFFER_SET_NAME,
                .verbosity_level = 0, // 设置日志等级
                .buffer_config = {
                    {
                        .buffer_name = LOG_BUFFER_NAME,
                        .num_pages = 4 // 16KB日志缓冲区
                    }},
                .max_retries = 3};

            // 创建slog2 buffer集实例
            int ret = slog2_register(&config, &handle, 0);
            if (ret != 0)
            {
                perror("slog2_register failed");
                return;
            }
        }
        void disable()
        {
            disableTag = true;
        }
        bool disableTag = false;
        void printLine(int level, const char *buf) override
        {
            if (disableTag)
                return;
            auto fmt = "%s\tunknow: %s\n";
            int slogLevel = SLOG2_INFO;

            switch (level)
            {
            case LOGGER_FATAL:
                fmt = "%s\t[fatal] %s\n";
                slogLevel = SLOG2_CRITICAL;
                break;
            case LOGGER_ERROR:
                fmt = "%s\t[error] %s\n";
                slogLevel = SLOG2_ERROR;
                break;
            case LOGGER_WARN:
                fmt = "%s\t[warn ] %s\n";
                slogLevel = SLOG2_WARNING;
                break;
            case LOGGER_INFO:
                fmt = "%s\t[info ] %s\n";
                slogLevel = SLOG2_INFO;
                break;
            case LOGGER_DEBUG:
                fmt = "%s\t[debug] %s\n";
                slogLevel = SLOG2_DEBUG1;
                break;
            case LOGGER_VERBOSE:
                fmt = "%s\t[verbo] %s\n";
                slogLevel = SLOG2_DEBUG2;
                break;
            default:
                break;
            }

            std::string timestamp = LoggerAppendData::getTimestamp();

            char *cbuf = (char *)malloc(1025);
            cbuf[1024] = '\0';
            snprintf(cbuf, 1024, fmt, timestamp.c_str(), buf);
            slog2c(handle, level, slogLevel, cbuf);

            free(cbuf);
            return;
        }
        void onUnload()
        {
        }
        virtual ~SLoggerSink()
        {
            // slog2_unregister(handle);
        }
        slog2_buffer_t handle;
    };
#endif // ONLY_FOR_QNX

    class LoggerFileSink : public ILoggerSink
    {
    public:
        void printLine(int level, const char *buf) override
        {
            if (!enableFile_)
            {
                return;
            }

            auto fmt = "%s\tunknow: %s\n";
            switch (level)
            {
            case LOGGER_FATAL:
                fmt = "%s\t[fatal] %s\n";
                break;
            case LOGGER_ERROR:
                fmt = "%s\t[error] %s\n";
                break;
            case LOGGER_WARN:
                fmt = "%s\t[warn ] %s\n";
                break;
            case LOGGER_INFO:
                fmt = "%s\t[info ] %s\n";
                break;
            case LOGGER_DEBUG:
                fmt = "%s\t[debug] %s\n";
                break;
            case LOGGER_VERBOSE:
                fmt = "%s\t[verbo] %s\n";
                break;
            default:
                break;
            }

            std::string timestamp = LoggerAppendData::getTimestamp();

            std::unique_lock<std::mutex> lock(_mutex);
            static char buffer[1024] = {0};
            sprintf(buffer, fmt, timestamp.c_str(), buf);

            std::shared_ptr<std::string> info = std::make_shared<std::string>();
            *info = buffer;

            _logList.push_back(info);
            _cv.notify_one();
            return;
        }

        std::streampos getFileSize(const std::string &filename)
        {
            std::ifstream file(filename, std::ios::binary | std::ios::ate);
            if (file.is_open())
            {
                std::streampos size = file.tellg();
                file.close();
                return size;
            }
            else
            {
                return 0;
            }
        }
        LoggerFileSink()
        {
            // printf("nowSize:%d\n", nowSize);
        }
        void init()
        {
            _logThread = std::thread(std::bind(&LoggerFileSink::syncToFile, this));
            nowSize = getFileSize(filePath);
            ofs.open(filePath.c_str(), std::ios::out | std::ios::app);
            if (!ofs.is_open())
            {
                printf("Unable to open file: %s\n", filePath.c_str());
                return;
            }
        }
        ~LoggerFileSink()
        {

            if (enableFile_)
            {
                _stop = true;
                _cv.notify_one();

                _logThread.join();
                ofs.flush();
                ofs.close();
                ofs.clear();
            }
        }

        void syncToFile()
        {
            while (!_stop)
            {
                std::shared_ptr<std::string> info;
                {

                    std::unique_lock<std::mutex> lock(_mutex);

                    _cv.wait(lock,
                             [&]() -> bool
                             { return !_logList.empty() || _stop; });
                    if (_stop)
                    {
                        break;
                    }
                    info = _logList.front();
                    _logList.pop_front();
                }
                if (info)
                    writeToFile(info->c_str(), info->size());

                if (nowSize >= EACH_FILE_SIZE)
                    rollOver();
            }

            std::unique_lock<std::mutex> lock(_mutex);

            for (auto i = _logList.begin(); i != _logList.end(); ++i)
            {
                writeToFile((*i)->c_str(), (*i)->size());
                if (nowSize >= EACH_FILE_SIZE)
                    rollOver();
            }
            _logList.clear();
        }
        void config(const char *folder, const char *filename, int eachMaxSize, int maxCount)
        {
            this->_fileName = filename;
            this->_log_file_folder = folder;
            if (_fileName.empty())
            {
                filePath = "./log.txt";
                _log_file_folder = ".";
                _fileName = "log.txt";
            }
            std::stringstream file;
            file << _log_file_folder << "/" << _fileName;
            filePath = file.str();
            MAX_FILE_COUNT = maxCount;
            EACH_FILE_SIZE = eachMaxSize;
            enableFile_ = true;
            init();
        }

        void rollOver()
        {
            if (!ofs.is_open())
            {
                printf("roll over failed\n");
                return;
            }

            ofs.flush();
            ofs.close();
            ofs.clear();
            for (int i = MAX_FILE_COUNT - 1; i >= 1; i--)
            {
                std::stringstream file;
                std::stringstream target;

                file << _log_file_folder << "/" << _fileName << (".") << i;
                target << _log_file_folder << "/" << _fileName << (".") << (i + 1);
                // printf(("Renaming file %s->%s\n"), file.str().c_str(), target.str().c_str());
                std::string aFileName = (file.str().c_str());
                std::string aTarget = (target.str().c_str());
                rename(aFileName.c_str(), aTarget.c_str());
            }

            std::stringstream ss;
            ss << _log_file_folder << "/" << _fileName << "." << 1;
            rename(filePath.c_str(), ss.str().c_str());
            nowSize = 0;
            ofs.open(filePath.c_str(), std::ios::out | std::ios::trunc);
            if (!ofs.is_open())
            {
                printf("Unable to open file: %s\n", filePath.c_str());
            }
        }

    private:
        std::list<std::shared_ptr<std::string>> _logList;

        std::thread _logThread;

        bool _stop = false;
        bool enableFile_ = false;

        std::mutex _mutex;

        std::condition_variable _cv;

        std::string filePath;

        void writeToFile(const char *info, int size)
        {
            if (ofs.is_open())
            {
                ofs << std::string(info, info + size);
                nowSize += size;
            }
        }

        std::string _fileName;
        std::string _log_file_folder;

        int MAX_FILE_COUNT = 10;
        int EACH_FILE_SIZE = 1024 * 1024;

        int nowSize = 0;

        std::ofstream ofs;
    };

    class Logger : public ILogger
    {
    public:
        void log(LoggerAppendDataInfo *data, LogLevelType nLogLevel, const std::string &content)
        {
            if (nLogLevel > (LogLevelType)logLevel)
            {
                return;
            }
            std::string prefix = append->toString(*data);
            char buf[1024] = {'\0'};
            sprintf(buf, "%s %s", prefix.c_str(), content.c_str());
            for (auto loggerSink : sinks)
            {
                loggerSink->printLine(nLogLevel, buf);
            }
        }

        void enableFile(const char *folder = "", const char *filename = "", int eachMaxSize = 1024 * 1024, int maxCount = 1)
        {
            if (fileSink)
                fileSink->config(folder, filename, eachMaxSize, maxCount);
        }
        void disableConsole()
        {
            if (consoleSink)
                consoleSink->disable();
        }
        void disableSLog()
        {
#if defined(__QNX__)
            if (slogSink)
                slogSink->disable();
#endif
        }
        Logger()
        {
            {
                consoleSink = new LoggerConsoleSink();

                sinks.push_back((ILoggerSink *)consoleSink);
            }
            {

                fileSink = new LoggerFileSink();

                sinks.push_back((ILoggerSink *)fileSink);
            }
#if defined(__QNX__)
            {
                slogSink = new SLoggerSink();
                sinks.push_back((ILoggerSink *)slogSink);
            }
#endif // QNX
            {
                LoggerAppendData *bean = new LoggerAppendData();

                append = (ILoggerAppendData *)bean;
            }
        }
        static Logger *Get()
        {
            static Logger sl;
            return &sl;
        }
        ~Logger()
        {
            for (auto i = sinks.begin(); i != sinks.end(); ++i)
            {
                if (*i)
                    delete *i;
            }
            sinks.clear();
        }

    private:
        void PrintList(LogLevelType nLogLevel, std::vector<std::string> &infos, const std::string &format) // 递归终止控制函数
        {
            // 尝试构建字符串序列
            replacePlaceholders(nLogLevel, infos, format);
        }

        template <class T, class... Args>
        void PrintList(LogLevelType nLogLevel, std::vector<std::string> &infos, const std::string &format, const T &val, Args... args)
        {
            std::stringstream ss;
            ss << val;
            infos.push_back(ss.str());
            PrintList(nLogLevel, infos, format, args...);
        }

    public:
        // using ("{}{}{}","A",1,2)
        template <class T, class... Args>
        void log(LogLevelType nLogLevel, const std::string &format, const T &val, Args... args)
        {
            std::vector<std::string> infos;
            PrintList(nLogLevel, infos, format, val, args...);
        }

        void replacePlaceholders(LogLevelType nLogLevel, std::vector<std::string> &infos, const std::string &format)
        {
            int state = 0;
            std::stringstream ss;
            int info_index = 0;
            std::string formatTag;
            for (auto i = format.begin(); i != format.end(); ++i)
            {
                if (*i == '{')
                {
                    state = 1;
                    formatTag = "";
                }
                else if (*i == '}' && state == 1)
                {
                    if (info_index < infos.size())
                    {
                        auto &info = infos[info_index];
                        if (formatTag == "HEX")
                        {
                            ss << "[";
                            for (auto i = info.begin(); i != info.end(); ++i)
                            {
                                if ((i + 1) != info.end())
                                    ss << "0x" << std::hex << (int)*i << " ";
                                else
                                    ss << "0x" << std::hex << (int)*i;
                            }

                            ss << "]";
                        }
                        else
                        {
                            ss << info;
                        }

                        info_index++;
                    }
                    state = 0;
                }
                else if (state == 1)
                {
                    formatTag.push_back(*i);
                }
                else
                {
                    ss << *i;
                }
            }
            LoggerAppendDataInfo lad;
            log(&lad, nLogLevel, ss.str());
        }

    private:
        int logLevel = LOGGER_DEBUG;
        std::string appendName;
        std::vector<ILoggerSink *> sinks;
        ILoggerAppendData *append = nullptr;

        LoggerFileSink *fileSink = nullptr;

        LoggerConsoleSink *consoleSink = nullptr;

#if defined(__QNX__)
        SLoggerSink *slogSink = nullptr;
#endif // QNX
    };
} // namespace log

#define theLOG simplelogger::Logger::Get()

#endif //_SIMPLE_LOGGER_H
